#include <Engine/StdAfx.h>
#include <Engine/GlobalEnv.h>
#include <Engine/IRenderer.h>
#include <Engine/ITimer.h>
#include <Engine/ITerrain.h>
#include <Engine/IMaterial.h>
#include <Engine/Foundation/Mouse.h>
#include <Engine/Foundation/Keyboard.h>
#include <Engine/Foundation/Engine.h>
#include <Engine/Renderer/Camera.h>
#include <Engine/Renderer/Shader.h>
#include <Engine/SceneGraph/Scene.h>
#include <Engine/Foundation/Font.h>
#include <Engine/Math/Vec2I.h>
#include <Engine/RenderObjects/DebugHud.h>
#include <Engine/Misc/Unicode.h>
#include <Engine/Misc/INIReader.h>
#include <Engine/Misc/StringUtils.h>
#include <FreeImage.h>


namespace fastbird
{
	CLASS_DECLSPEC  GlobalEnv* gEnv=0;

	//------------------------------------------------------------------------
	IEngine* IEngine::CreateInstance()
	{
		return new Engine();
	}

	//------------------------------------------------------------------------
	void IEngine::Log(const char* szFmt, ...)
	{
		static char buf[1024];

		va_list args;
		va_start(args, szFmt);
		vsprintf_s(buf, 1024, szFmt, args);
		va_end(args);
		strcat_s(buf, 1024, "\n");

		OutputDebugString(buf);
	}

	//------------------------------------------------------------------------
	void IEngine::LogLastError(const char* file, int line, const char* function)
	{
		static char buf[1024];

		DWORD err = GetLastError();
		if (err==0)
			return;

		LPVOID lpMsgBuf;

		FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
			FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
			(LPTSTR) &lpMsgBuf, 0, NULL );

		sprintf_s(buf, 1024, "%s(%d): %s() - %s - %s \n", file, line, function, lpMsgBuf);
		OutputDebugString(buf);


		LocalFree(lpMsgBuf);
	}

	//------------------------------------------------------------------------
	Engine::Engine()
	{
		gEnv = new GlobalEnv;
		mTimer = ITimer::CreateTimer();
		gEnv->pEngine = this;
		gEnv->pTimer = mTimer.get();	
		mEngineCamera = 0;
        mScene = new Scene();
		mFont = new Font();

		m_hWnd = 0;
		mFileChangeThreadFinished = 0;
		mExiting = false;
		mFileMonitorThread = 0;
	}

	//------------------------------------------------------------------------
	Engine::~Engine()
	{
		mExiting = true;
		QueueUserAPC((PAPCFUNC)TerminateFileMonitor, mFileMonitorThread, 0);
		DWORD ret = WaitForSingleObject(mFileChangeThreadFinished, 1000);
		if (ret==WAIT_FAILED)
		{
			FB_LOG_LAST_ERROR();
		}
		CloseHandle(mFileChangeThreadFinished);
		
		mFont = 0;
		mTerrain=0;

		CAMERA_VECTOR::iterator it = m_pCameras.begin(), itEnd = m_pCameras.end();
		for (; it!=itEnd; it++)
		{
			delete *it;
		}
		delete gEnv;
		FreeImage_DeInitialise();
	}

	//------------------------------------------------------------------------
	void Engine::GetGlobalEnv(GlobalEnv** outGlobalEnv)
	{
		*outGlobalEnv = gEnv;
	}

	//------------------------------------------------------------------------
	HWND Engine::CreateEngineWindow(int x, int y, int width, int height, 
		const char* title, WNDPROC winProc)
	{
		width+=16;
		height+=38;
		const char* const myclass = "myclass" ;
		WNDCLASSEX wndclass = { sizeof(WNDCLASSEX), CS_DBLCLKS, winProc,
								0, 0, GetModuleHandle(0), LoadIcon(0,IDI_APPLICATION),
								LoadCursor(0,IDC_ARROW), HBRUSH(COLOR_WINDOW+1),
								0, myclass, LoadIcon(0,IDI_APPLICATION) } ;

		if( RegisterClassEx(&wndclass) )
		{
			m_hWnd = CreateWindowEx( 0, myclass, title,
					   WS_OVERLAPPEDWINDOW, x, y,
					   width, height, 0, 0, GetModuleHandle(0), 0 ) ;

			HWND console = GetConsoleWindow();
			RECT consoleRect;
			GetWindowRect(console, &consoleRect);
			MoveWindow(console, x+width, y, consoleRect.right-consoleRect.left, 
				consoleRect.bottom-consoleRect.top, TRUE);

			return m_hWnd;
		}

		return 0;
	}

	//------------------------------------------------------------------------
	HWND Engine::GetWindowHandle() const
	{
		return m_hWnd;
	}

	//------------------------------------------------------------------------
	bool Engine::InitEngine(RENDERER_TYPE rendererType)
	{
		FreeImage_Initialise();
		bool successful = true;
		switch(rendererType)
		{
		case D3D9:
			successful = InitDirectX9();
			break;

		case D3D11:
			successful = InitDirectX11();
			break;

		case OPENGL:
			successful = InitOpenGL();
			break;

		default:
			Log(FB_DEFAULT_DEBUG_ARG, "Not supported rendererType!");
			return false;
		}
		mEngineCamera = CreateAndRegisterCamera("Engine_Camera");
		mCurrentCamera = mEngineCamera;
		mRenderer->SetCamera(mEngineCamera);
		mFont->Init("Data/fonts/nanum_pen.fnt");
		mFont->SetTextEncoding(IFont::UTF16);
		mDebugHud = new DebugHud;
		RegisterMouseAndKeyboard();

		mINI = new INIReader("Engine.ini");
		if (mINI->GetError())
		{
			IEngine::Log(FB_DEFAULT_DEBUG_ARG, "Could not parse the Engine.ini file!");
			assert(0);
		}

		//mShaderWatchDir = "code/engine/renderer/shaders";
		mShaderWatchDir = ".";

		mFileMonitorThread = CreateThread( 0, 0, (LPTHREAD_START_ROUTINE)FileChangeMonitorProc, 0, 0, 0) ;
		if (mFileMonitorThread == INVALID_HANDLE_VALUE)
		{
			Log(FB_DEFAULT_DEBUG_ARG, "Failed to create the FileChangeNotifier thread!");
		}

		return successful;
	}

	inline IRenderer* Engine::GetRenderer() const
	{
		return mRenderer;
	}

	inline IScene* Engine::GetScene() const
	{
		return mScene;
	}

	inline IFont* Engine::GetFont() const
	{
		return mFont;
	}

	bool Engine::InitDirectX9()
	{
		Log(FB_DEFAULT_DEBUG_ARG, "Initializing DirectX9...");

		Log(FB_DEFAULT_DEBUG_ARG, "DirectX9 Initialized.");
		return true;
	}

	bool Engine::InitDirectX11()
	{
		Log(FB_DEFAULT_DEBUG_ARG, "Initializing DirectX11...");

		mRenderer = IRenderer::CreateD3D11Instance();
		if (!mRenderer)
			return false;

		bool success = mRenderer->Init();

		Log(FB_DEFAULT_DEBUG_ARG, "DirectX11 Initialized.");

		return success;
	}

	bool Engine::InitOpenGL()
	{
		Log(FB_DEFAULT_DEBUG_ARG, "Initializing OpenGL...");

		Log(FB_DEFAULT_DEBUG_ARG, "OpenGL initialized...");

		return true;
	}

	bool Engine::RegisterMouseAndKeyboard()
	{
		mMouse = new Mouse;
		mKeyboard = new Keyboard;
#ifdef _FBENGINE_FOR_WINDOWS_
		const unsigned short HID_USAGE_PAGE_GENERIC = 0x01;
		const unsigned short HID_USAGE_GENERIC_MOUSE = 0x02;
		const unsigned short HID_USAGE_GENERIC_KEYBOARD = 0x06;
		
		RAWINPUTDEVICE Rid[2];
		Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC; 
		Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE; 
		Rid[0].dwFlags = 0;   
		Rid[0].hwndTarget = m_hWnd;
		Rid[1].usUsagePage = HID_USAGE_PAGE_GENERIC; 
		Rid[1].usUsage = HID_USAGE_GENERIC_KEYBOARD; 
		Rid[1].dwFlags = 0;   
		Rid[1].hwndTarget = m_hWnd;

		HRESULT hr = RegisterRawInputDevices(Rid, 2, sizeof(Rid[0]));
		if (FAILED(hr))
		{
			Log(FB_DEFAULT_DEBUG_ARG, "Registering Raw input devices failed!");
			return false;
		}
		
		return true;
#elif _FBENGINE_FOR_LINUX_

#endif
	}

	void Engine::UpdateFrame(float dt)
	{
		// Send User Input
		HandleUserInput();

		mTerrain->Update();

		// Update physics
	
		// Update Entities

		// Update AI

		// Simulations

		// Render
		Render(dt);

		mMouse->EndFrame();
		mKeyboard->EndFrame();
		gEnv->mFrameCounter++;
	}

	void Engine::HandleUserInput()
	{
		if (mKeyboard->IsKeyPressed('W') && mKeyboard->IsKeyDown(VK_CONTROL))
		{
			bool wire = GetRenderer()->GetWireframe();
			GetRenderer()->SetWireframe(!wire);
		}
		

		INPUT_LISTENER_VECTOR::iterator it = mInputListeners.begin(),
			itEnd = mInputListeners.end();
		for (; it!=itEnd; it++)
		{
			(*it)->OnInput(mMouse.get(), mKeyboard.get());
		}
	}

	void Engine::PreRender(float dt)
	{
		mScene->PreRender();
	}

	void Engine::Render(float dt)
	{
		mRenderer->InitFrameProfiler(dt);
		mRenderer->Clear(0, 0, 1, 1);
		mRenderer->UpdateFrameConstantsBuffer();

		// PreRender Everything
		PreRender(dt);
	
		// Render Scene
		mScene->Render();

		RenderText();

		// Render Profiler
		RenderFrameProfiler();

		mRenderer->Present();
	}

	bool Engine::CreateTerrain(int numVertX, int numVertY, float distance, const char* heightmapFile)
	{
		mTerrain = ITerrain::CreateInstance(numVertX, numVertY, distance, heightmapFile);
		return true;
	}

	ICamera* Engine::CreateAndRegisterCamera(const char* cameraName)
	{
		ICamera* pCamera = new Camera();
		RegisterCamera(cameraName, pCamera);
		return pCamera;
	}

	void Engine::RegisterCamera(const char* cameraName, ICamera* pCamera)
	{
		pCamera->SetName(cameraName);
		m_pCameras.push_back(pCamera);
	}

	struct InputListenerSorter
	{
		bool operator() (IInputListener* a, IInputListener*b)
		{
			if (a->mFBInputListenerCategory < b->mFBInputListenerCategory)
			{
				return true;
			}
			else if (a->mFBInputListenerCategory == b->mFBInputListenerCategory)
			{
				if (a->mFBInputListenerPriority > b->mFBInputListenerPriority)
				{
					return true;
				}
				else if (a->mFBInputListenerPriority == b->mFBInputListenerPriority)
				{
					IEngine::Log(FB_DEFAULT_DEBUG_ARG, "Input listeners has the same priority. sorted by address.");
					return a<b;
				}
				else
				{
					return false;
				}
			}
			else
			{
				return false;
			}
		}
	};

	void Engine::AddInputListener(IInputListener* pInputListener, 
			IInputListener::INPUT_LISTEN_CATEGORY category, int priority)
	{
		pInputListener->mFBInputListenerCategory = category;
		pInputListener->mFBInputListenerPriority = priority;

		mInputListeners.push_back(pInputListener);
		InputListenerSorter sorter;
		std::sort(mInputListeners.begin(), mInputListeners.end(), sorter);

	}

#ifdef _FBENGINE_FOR_WINDOWS_
	bool Engine::WinProc( HWND window, UINT msg, WPARAM wp, LPARAM lp )
	{
		switch(msg)
		{
		case WM_INPUT:
			{
				UINT dwSize = 40;
				static BYTE lpb[40];
    
				GetRawInputData((HRAWINPUT)lp, RID_INPUT, 
								lpb, &dwSize, sizeof(RAWINPUTHEADER));
    
				RAWINPUT* raw = (RAWINPUT*)lpb;
    
				switch(raw->header.dwType)
				{
				case RIM_TYPEMOUSE:
					{
						mMouse->PushEvent( *((MouseEvent*)&raw->data.mouse));
					}
					break;
				case RIM_TYPEKEYBOARD:
					{
						mKeyboard->PushEvent(*((KeyboardEvent*)&raw->data.keyboard));
					}
					break;
				}
			}
			break;
		}
		return 0;
	}
#elif _FBENGINE_FOR_LINUX_

#endif

	//----------------------------------------------------------------------------
	void Engine::QueueText(const Vec2I& pos, WCHAR* text, const Vec3& color, float size)
	{
		mDebugHud->QueueText(pos, text, color, size);
	}

	//----------------------------------------------------------------------------
	void Engine::RenderText()
	{
		bool backup = mRenderer->GetWireframe();
		mRenderer->SetWireframe(false);
		mDebugHud->Render();
		mRenderer->SetWireframe(backup);
	}

	//----------------------------------------------------------------------------
	void Engine::RenderFrameProfiler()
	{
		char msg[255];
		int x = 2;
		int y=10;
		int yStep = 16;
		const RENDERER_FRAME_PROFILER& profiler = mRenderer->GetFrameProfiler();

		sprintf_s(msg, 255, "FrameRate = %.0f", profiler.FrameRateDisplay); 
		QueueText(Vec2I(x, y), AnsiToWide(msg), Vec3(1, 1, 1), 1.f);
		y+=yStep;

		sprintf_s(msg, 255, "Num draw calls = %d", profiler.NumDrawCall); 
		QueueText(Vec2I(x, y), AnsiToWide(msg), Vec3(1, 1, 1), 1.f);
		y+=yStep;

		sprintf_s(msg, 255, "Num vertices = %d", profiler.NumVertexCount);
		QueueText(Vec2I(x, y), AnsiToWide(msg), Vec3(1, 1, 1), 1.f);
		y+=yStep*2;

		
		sprintf_s(msg, 255, "Num draw indexed calls = %d", profiler.NumDrawIndexedCall);
		QueueText(Vec2I(x, y), AnsiToWide(msg), Vec3(1, 1, 1), 1.f);
		y+= yStep;

		sprintf_s(msg, 255, "Num draw indices = %d", profiler.NumIndexCount);
		QueueText(Vec2I(x, y), AnsiToWide(msg), Vec3(1, 1, 1), 1.f);		
	}

	//----------------------------------------------------------------------------
	void Engine::StartFileChangeMonitorThread()
	{
		mFileChangeThreadFinished = CreateEvent(0, FALSE, FALSE, "FileChangeThreadFinished");

		mMonitoringDirectory = CreateFile(mShaderWatchDir.c_str(), FILE_LIST_DIRECTORY, 
			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
			0, OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0);
		if (mMonitoringDirectory==INVALID_HANDLE_VALUE)
		{
			Log(FB_DEFAULT_DEBUG_ARG, "Cannot open the shader watch directory!");
			FB_LOG_LAST_ERROR();
			return;
		}
		const int BUFFER_SIZE = sizeof(DWORD)*100;
		mFileChangeBuffer.resize(BUFFER_SIZE);
		mFileChangeBackupBuffer.resize(BUFFER_SIZE);
		MonitorFileChange();

		while(!mExiting)
		{
			DWORD rc = ::SleepEx(INFINITE, true);
		} 
		
		SetEvent(mFileChangeThreadFinished);
	}

	//----------------------------------------------------------------------------
	void Engine::MonitorFileChange()
	{
		DWORD writtenBytes=0;
		BOOL successful = ReadDirectoryChangesW(mMonitoringDirectory, &mFileChangeBuffer[0], mFileChangeBuffer.size(),
							true, FILE_NOTIFY_CHANGE_LAST_WRITE, &writtenBytes, &mOverlapped, NotificationCompletion);
		if (!successful)
		{
			Log(FB_DEFAULT_DEBUG_ARG, "ReadDirectoryChangesW Failed!");
			DWORD lastError = GetLastError();
			Log("\t error code = %d", lastError);
			return;
		}
	}

	//----------------------------------------------------------------------------
	void Engine::BackupFileChangeBuffer(size_t size)
	{
		memcpy(&mFileChangeBackupBuffer[0], &mFileChangeBuffer[0], size);
	}

	//----------------------------------------------------------------------------
	void Engine::ProcessFileChange()
	{
		FILE_NOTIFY_INFORMATION* pFNI = (FILE_NOTIFY_INFORMATION*)&mFileChangeBackupBuffer[0];
		while(pFNI)
		{
			switch(pFNI->Action)
			{
			case FILE_ACTION_MODIFIED:
				{
					char fileName[MAX_PATH];
					int count = WideCharToMultiByte(CP_ACP, 0, pFNI->FileName, pFNI->FileNameLength / sizeof(WCHAR),
						fileName, _ARRAYSIZE(fileName)-1, 0, 0);
					fileName[count]=0;
					UnifyFilepath(fileName);
					if (CheckExtension(fileName, "hlsl"))
						Shader::ReloadShader(fileName);
					else if (CheckExtension(fileName, "material"))
						IMaterial::ReloadMaterial(fileName);
				}
				break;
			}
			pFNI = pFNI->NextEntryOffset ? pFNI + pFNI->NextEntryOffset : 0;
		}
	}
	//----------------------------------------------------------------------------
	void Engine::CleanFileChangeMonitor()
	{
		::CancelIo(mMonitoringDirectory);
		::CloseHandle(mMonitoringDirectory);
		mMonitoringDirectory = 0;
	}

	//----------------------------------------------------------------------------
	DWORD CALLBACK Engine::FileChangeMonitorProc(LPVOID handle)
	{
		Engine* pEngine = (Engine*)gEnv->pEngine;
		pEngine->StartFileChangeMonitorThread();

		return 0;
	}

	//----------------------------------------------------------------------------
	VOID CALLBACK Engine::NotificationCompletion(
			DWORD dwErrorCode,							// completion code
			DWORD dwNumberOfBytesTransfered,			// number of bytes transferred
			LPOVERLAPPED lpOverlapped)					// I/O information buffer
	{
		Engine* pEngine = (Engine*)gEnv->pEngine;
		if (dwErrorCode == ERROR_OPERATION_ABORTED)
		{
			return;
		}
		assert(dwNumberOfBytesTransfered >= offsetof(FILE_NOTIFY_INFORMATION, FileName) + sizeof(WCHAR));
		if(!dwNumberOfBytesTransfered)
			return;

		pEngine->BackupFileChangeBuffer(dwNumberOfBytesTransfered);		
		pEngine->MonitorFileChange();
		pEngine->ProcessFileChange();

	}

	//----------------------------------------------------------------------------
	VOID CALLBACK Engine::TerminateFileMonitor(ULONG_PTR Parameter)
	{
		Engine* pEngine = (Engine*)gEnv->pEngine;
		pEngine->CleanFileChangeMonitor();
	}

	

} // namespace fastbird